/*
 * TeleStax, Open Source Cloud Communications
 * Copyright 2011-2014, Telestax Inc and individual contributors
 * by the @authors tag.
 *
 * This program is free software: you can redistribute it and/or modify
 * under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

package org.restcomm.media.core.rtcp;

import java.util.Date;
import java.util.List;

import org.apache.commons.net.ntp.TimeStamp;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.restcomm.media.core.rtp.statistics.RtpMember;
import org.restcomm.media.core.rtp.statistics.RtpStatistics;

/**
 * Factory for building RTCP packets
 * 
 * @author Henrique Rosa (henrique.rosa@telestax.com)
 * 
 */
public class RtcpPacketFactory {
	
	public static final Logger logger = LogManager.getLogger(RtcpPacketFactory.class);

	/**
	 * Builds a packet containing an RTCP Sender Report.
	 * 
	 * @param statistics
	 *            The statistics of the RTP session
	 * @return The RTCP packet
	 */
	private static RtcpSenderReport buildSenderReport(RtpStatistics statistics, boolean padding) {
		/*
		 *         0                   1                   2                   3
         *         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
         *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         * header |V=2|P|    RC   |   PT=SR=200   |             length            |
         *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *        |                         SSRC of sender                        |
         *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
         * sender |              NTP timestamp, most significant word             |
         * info   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *        |             NTP timestamp, least significant word             |
         *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *        |                         RTP timestamp                         |
         *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *        |                     sender's packet count                     |
         *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *        |                      sender's octet count                     |
         *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
         * report |                 SSRC_1 (SSRC of first source)                 |
         * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *   1    | fraction lost |       cumulative number of packets lost       |
         *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *        |           extended highest sequence number received           |
         *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *        |                      interarrival jitter                      |
         *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *        |                         last SR (LSR)                         |
         *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *        |                   delay since last SR (DLSR)                  |
         *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
         * report |                 SSRC_2 (SSRC of second source)                |
         * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *   2    :                               ...                             :
         *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
         *        |                  profile-specific extensions                  |
         *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		 */
		long ssrc = statistics.getSsrc();
		long currentTime = statistics.getCurrentTime();
		TimeStamp ntpTs = new TimeStamp(new Date(currentTime));
		long ntpSec = ntpTs.getSeconds();
		long ntpFrac = ntpTs.getFraction();
		long elapsedTime = statistics.getCurrentTime() - statistics.getRtpSentOn();
		long rtpTs = statistics.getRtpTimestamp() + statistics.getRtpTime(elapsedTime);
		long psent = statistics.getRtpPacketsSent();
		long osent = statistics.getRtpOctetsSent();
		
		RtcpSenderReport senderReport = new RtcpSenderReport(padding, ssrc, ntpSec, ntpFrac, rtpTs, psent, osent);
		
		// Add receiver reports for each registered member
		List<Long> members = statistics.getMembersList();
		for (Long memberSsrc : members) {
			if (ssrc != memberSsrc) {
				RtpMember memberStats = statistics.getMember(memberSsrc.longValue());
				RtcpReportBlock rcvrReport = buildSubReceiverReport(memberStats);
				senderReport.addReceiverReport(rcvrReport);
			}
		}
		return senderReport;
	}
	
	/**
	 * Builds a packet containing an RTCP Receiver Report
	 * 
	 * @param statistics
	 *            The statistics of the RTP session
	 * @return The RTCP packet
	 */
	private static RtcpReceiverReport buildReceiverReport(RtpStatistics statistics, boolean padding) {
		RtcpReceiverReport report = new RtcpReceiverReport(padding, statistics.getSsrc());
		long ssrc = statistics.getSsrc();
		
		// Add receiver reports for each registered member
		List<Long> members = statistics.getMembersList();
		for (Long memberSsrc : members) {
			if (ssrc != memberSsrc) {
				RtpMember memberStats = statistics.getMember(memberSsrc.longValue());
				RtcpReportBlock rcvrReport = buildSubReceiverReport(memberStats);
				report.addReceiverReport(rcvrReport);
			}
		}
		return report;
	}
	
	private static RtcpSdes buildSdes(RtpStatistics statistics, boolean padding) {
		RtcpSdes sdes = new RtcpSdes(padding);
		RtcpSdesChunk chunk = new RtcpSdesChunk(statistics.getSsrc());
		RtcpSdesItem cname = new RtcpSdesItem(RtcpSdesItem.RTCP_SDES_CNAME, statistics.getCname());
//		RtcpSdesItem end = new RtcpSdesItem(RtcpSdesItem.RTCP_SDES_END, null);
		chunk.addRtcpSdesItem(cname);
//		chunk.addRtcpSdesItem(end);
		sdes.addRtcpSdesChunk(chunk);
		
		return sdes;
	}
	
	private static RtcpReportBlock buildSubReceiverReport(RtpMember statistics) {
		long ssrc = statistics.getSsrc();
		int fraction = (int) statistics.getFractionLost();
		int lost = (int) statistics.getPacketsLost();
		int seqNumCycle = statistics.getSequenceCycle();
		long lastSeq = statistics.getExtHighSequence();
		int jitter = (int) statistics.getJitter();
		long lsr = statistics.getLastSR();
		long dlsr = statistics.getLastSRdelay();
		
		return new RtcpReportBlock(ssrc, fraction, lost, seqNumCycle, lastSeq, jitter, lsr, dlsr);
	}

	/**
	 * Builds a packet containing an RTCP Report.
	 * 
	 * RTP receivers provide reception quality feedback using RTCP report
	 * packets which may take one of two forms depending upon whether or not the
	 * receiver is also a sender. The only difference between the sender report
	 * (SR) and receiver report (RR) forms, besides the packet type code, is
	 * that the sender report includes a 20-byte sender information section for
	 * use by active senders. The SR is issued if a site has sent any data
	 * packets during the interval since issuing the last report or the previous
	 * one, otherwise the RR is issued.
	 * 
	 * Both the SR and RR forms include zero or more reception report blocks,
	 * one for each of the synchronization sources from which this receiver has
	 * received RTP data packets since the last report. Reports are not issued
	 * for contributing sources listed in the CSRC list. Each reception report
	 * block provides statistics about the data received from the particular
	 * source indicated in that block.
	 * 
	 * Since a maximum of 31 reception report blocks will fit in an SR or RR
	 * packet, additional RR packets SHOULD be stacked after the initial SR or
	 * RR packet as needed to contain the reception reports for all sources
	 * heard during the interval since the last report. If there are too many
	 * sources to fit all the necessary RR packets into one compound RTCP packet
	 * without exceeding the MTU of the network path, then only the subset that
	 * will fit into one MTU SHOULD be included in each interval. The subsets
	 * SHOULD be selected round-robin across multiple intervals so that all
	 * sources are reported.
	 * 
	 * @param statistics
	 *            The statistics of the RTP session
	 * @return The RTCP packet containing the RTCP Report (SS or RR).
	 */
	public static RtcpPacket buildReport(RtpStatistics statistics) {
		// TODO Validate padding
		boolean padding = false;
		
		// Build the initial report packet
		RtcpReport report; 
		if(statistics.hasSent()) {
			report = buildSenderReport(statistics, padding);
		} else {
			report = buildReceiverReport(statistics, padding);
		}
		
		// Build the SDES packet containing the CNAME
		RtcpSdes sdes = buildSdes(statistics, padding);
		
		// Build the compound packet
		return new RtcpPacket(report, sdes);
	}

	/**
	 * Builds a packet containing an RTCP BYE message.
	 * 
	 * @param statistics
	 *            The statistics of the RTP session
	 * @return The RTCP packet
	 */
	public static RtcpPacket buildBye(RtpStatistics statistics) {
		// TODO Validate padding
		boolean padding = false;
		
		// Build the initial report packet
		RtcpReport report; 
		if(statistics.hasSent()) {
			report = buildSenderReport(statistics, padding);
		} else {
			report = buildReceiverReport(statistics, padding);
		}
		
		// Build the SDES packet containing the CNAME
		RtcpSdes sdes = buildSdes(statistics, padding);
		
		// Build the BYE
		RtcpBye bye = new RtcpBye(padding);
		bye.addSsrc(statistics.getSsrc());
		
		// Build the compound packet
		return new RtcpPacket(report, sdes, bye);
	}
	
	public static RtcpPacket buildPacket(RtcpPacketType packetType, RtpStatistics statistics) {
		switch (packetType) {
		case RTCP_REPORT:
			return buildReport(statistics);
			
		case RTCP_BYE:
			return buildBye(statistics);

		default:
			throw new IllegalArgumentException("Unsupported RTCP packet type.");
		}
	}
}
